﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using EvoSim.RandomNumberGenerators;
using System.Diagnostics;
using EvoSim.Simulation.Genes;
using EvoSim.Simulation.NeuralNets.SpikeNet;

namespace EvoSim.Simulation
{
	public class Creature : Entity
	{
		public INeuralNet Brain;
		public Genome CreatureGenome;

		public int EyeNeurons;
		public double EyeSpan = Math.PI;
		public int ViewDistance = 1000;
		//EntityColor
		public double Speed;
		public double RotationSpeed;
		public DoubleMinMax Energy = new DoubleMinMax(0, 0);

		public bool CanSeeCreatures = true;

		public double EnergyLossByTick = 1;
		public double EnergyLossByRotation = 1;
		public double EnergyLossByMove = 1;
		public double EnergyLossByEating = 1;
		public double EnergyLossByMating = 60 * 60;
		public double EatingBiteSize = 100;

		public Creature MatingCandidate;
		public int CyclesInMating = 0;
		public int CyclesNeededForMating = 10;
		public int MaxAge = 60 * 60 * 10;

		#region Neurons
		public IInputNeuron[] EyeR_I;
		public IInputNeuron[] EyeG_I;
		public IInputNeuron[] EyeB_I;
		public IInputNeuron[] EyeDistance_I;

		protected IInputNeuron random_I;
		protected IInputNeuron clock_I;
		protected IInputNeuron energy_I;

		protected IOutputNeuron rotateLeft_O;
		protected IOutputNeuron rotateRight_O;
		protected IOutputNeuron moveForward_O;
		protected IOutputNeuron moveBackwards_O;
		protected IOutputNeuron eat_O;
		protected IOutputNeuron mate_O;

		#endregion

		public double PixelsRotatedLeft;
		public double PixelsRotatedRight;
		public double PixelsMovedForwards;
		public double PixelsMovedBackwards;
		public int TimesHaveEaten;
		public int TimesMated;

		public bool LoseEnergy = true;
		public bool IsThinking = true;
		public bool DisableMating = false;

		public Creature()
			: base("Creature")
		{
		}

		public virtual void Randomize()
		{
			//No BodyGenes

			CreatureGenome.Randomize(3, 3, 6, 20);

			int i = 0;
			CreatureGenome.NetChromosome.Outputs[i++].Name = "Rotate Left";
			CreatureGenome.NetChromosome.Outputs[i++].Name = "Rotate Right";
			CreatureGenome.NetChromosome.Outputs[i++].Name = "Move Forward";
			CreatureGenome.NetChromosome.Outputs[i++].Name = "Move Backward";
			CreatureGenome.NetChromosome.Outputs[i++].Name = "Eat";
			CreatureGenome.NetChromosome.Outputs[i++].Name = "Mate";

			ImprintGenome(CreatureGenome);
			Energy.SetToMax();
		}

		public virtual Creature GetNewInstance()
		{
			return new Creature();
		}

		public Creature CreateOffspring(Creature parent2, double posX, double posY)
		{
			return CreateOffspring(GetNewInstance(), parent2, posX, posY);
		}
		public virtual Creature CreateOffspring(Creature child, Creature parent2, double posX, double posY)
		{
			var parent1 = this;
			child.World = parent1.World;
			child.ImprintGenome(parent1.CreatureGenome.GetChildGenome(parent2.CreatureGenome));
			child.PosX = posX;
			child.PosY = posY;

			Energy.Value /= 2;
			child.Energy.Value = Energy.Value;

			return child;
		}

		protected virtual void HandleInputNeurons()
		{
			HandleEyeInput();

			random_I.FireFrequency = Globals.Random.NextDouble(0, 1);

			//clock_I
			energy_I.FireFrequency = 1 - Energy.Value / Energy.Max;
		}
		protected virtual void HandleEyeInput()
		{
			const double tau = Math.PI * 2;

			double rotationStep = EyeSpan / EyeNeurons;
			double rotation = (Rotation - EyeSpan / 2) + rotationStep;

			if (rotation < 0)
				rotation = tau + rotation;
			else if (rotation > tau)
				rotation = rotation - tau;

			for (int i = 0; i < EyeNeurons; i++)
			{
				double distance = -1;
				double r = -1;
				double g = -1;
				double b = -1;

				foreach (Entity entity in World.EntityList)
				{
					if (entity == this)
						continue;

					if (!CanSeeCreatures && entity.EntityName == "Creature")
						continue;

					double dx = entity.PosX - PosX;
					double dy = entity.PosY - PosY;
					double entityDistance = Math.Sqrt(dx * dx + dy * dy);

					double angle = Math.Acos(dx / entityDistance);

					if (dy < 0)
						angle = tau - angle;

					double rotationDistance = rotation - angle;
					rotationDistance = Math.Min((angle + tau) - rotation, rotationDistance);

					if (rotationDistance < 0)
						rotationDistance = tau + rotationDistance;

					if (rotationDistance >= 0 &&
						rotationDistance < rotationStep &&
						entityDistance < ViewDistance &&
						(distance == -1 || entityDistance < distance))
					{
						distance = entityDistance;
						r = entity.EntityColor.R / (double)255;
						g = entity.EntityColor.G / (double)255;
						b = entity.EntityColor.B / (double)255;
					}
				}

				if (distance != -1)
					EyeDistance_I[i].FireFrequency = 1 - distance / ViewDistance;
				else
					EyeDistance_I[i].FireFrequency = -1;
				EyeR_I[i].FireFrequency = r;
				EyeG_I[i].FireFrequency = g;
				EyeB_I[i].FireFrequency = b;

				rotation += rotationStep;
				if (rotation < 0)
					rotation = tau + rotation;
				else if (rotation > tau)
					rotation = rotation - tau;
			}
		}

		public virtual void ImprintGenome(Genome genome)
		{
			CreatureGenome = genome;

			EyeNeurons = genome.NetChromosome.EyeNeuronsCount;

			Brain = genome.NetChromosome.GetNeuralNet();
			genome.NetChromosome.Mutate();
			int i = 0;
			EyeR_I = GetListSliceAsArray(i, i += EyeNeurons, Brain.Inputs);
			EyeG_I = GetListSliceAsArray(i, i += EyeNeurons, Brain.Inputs);
			EyeB_I = GetListSliceAsArray(i, i += EyeNeurons, Brain.Inputs);
			EyeDistance_I = GetListSliceAsArray(i, i += EyeNeurons, Brain.Inputs);

			random_I = Brain.Inputs[i++];
			clock_I = Brain.Inputs[i++];
			energy_I = Brain.Inputs[i++];

			i = 0;
			rotateLeft_O = Brain.Outputs[i++];
			rotateRight_O = Brain.Outputs[i++];
			moveForward_O = Brain.Outputs[i++];
			moveBackwards_O = Brain.Outputs[i++];
			eat_O = Brain.Outputs[i++];
			mate_O = Brain.Outputs[i++];

			i = 0;
			//BodyGenes
		}

		public override void Update(Microsoft.Xna.Framework.GameTime gameTime)
		{
			base.Update(gameTime);

			Energy.Value -= EnergyLossByTick;

			HandleInputNeurons();

			if (IsThinking)
				Brain.Update();

			//Rotate
			double rotationDistance = rotateLeft_O.Output * RotationSpeed;
			Rotation -= rotationDistance;
			Energy.Value -= rotationDistance * EnergyLossByRotation;
			PixelsRotatedLeft += rotationDistance;

			rotationDistance = rotateRight_O.Output * RotationSpeed;
			Rotation += rotationDistance;
			Energy.Value -= rotationDistance * EnergyLossByRotation;
			PixelsRotatedRight += rotationDistance;

			//Move
			double moveDistance = moveForward_O.Output * Speed;
			PosX += moveDistance * Math.Cos(Rotation);
			PosY += moveDistance * Math.Sin(Rotation);
			Energy.Value -= moveDistance * EnergyLossByMove;
			PixelsMovedForwards += moveDistance;

			moveDistance = -moveBackwards_O.Output * Speed;
			PosX += moveDistance * Math.Cos(Rotation);
			PosY += moveDistance * Math.Sin(Rotation);
			Energy.Value -= moveDistance * EnergyLossByMove;
			PixelsMovedBackwards += moveDistance;

			//Eat
			if (true)//(eat_O.Output > 0.5)
			{
				Energy.Value -= EnergyLossByEating;

				foreach (Entity entity in World.EntityList)
				{
					if (entity == this)
						continue;

					var entityRect = new Rectangle((int)entity.PosX - entity.Radius / 2, (int)entity.PosY - entity.Radius / 2, entity.Radius, entity.Radius);
					var thisRect = new Rectangle((int)PosX - Radius / 2, (int)PosY - Radius / 2, Radius, Radius);

					if (entityRect.Intersects(thisRect) &&
						entity.EntityName == "Food")
					{
						Food food = (Food)entity;

						TimesHaveEaten += 1;

						if (food.EnergyStorage >= EatingBiteSize)
						{
							food.EnergyStorage -= EatingBiteSize;
							Energy.Value += EatingBiteSize;
						}
						else
						{
							Energy.Value += food.EnergyStorage;
							food.EnergyStorage = 0;
						}
					}
				}
			}
			//Mate
			if (!DisableMating && mate_O.Output > 0.5)
			{
				if (MatingCandidate != null)
				{
					var entityRect = new Rectangle((int)MatingCandidate.PosX, (int)MatingCandidate.PosY, MatingCandidate.Radius, MatingCandidate.Radius);
					var thisRect = new Rectangle((int)PosX, (int)PosY, Radius, Radius);

					if (entityRect.Intersects(thisRect))
						CyclesInMating += 1;

					if (CyclesInMating >= CyclesNeededForMating)
					{
						TimesMated += 1;

						Energy.Value -= EnergyLossByMating;
						World.AddEntity(CreateOffspring(MatingCandidate, this.PosX, this.PosY));
						MatingCandidate = null;
						CyclesInMating = 0;
					}
				}
				else
				{
					CyclesInMating = 0;

					foreach (Entity entity in World.EntityList)
					{
						if (entity == this)
							continue;

						Rectangle entityRect = new Rectangle((int)entity.PosX, (int)entity.PosY, entity.Radius, entity.Radius);
						Rectangle thisRect = new Rectangle((int)PosX, (int)PosY, Radius, Radius);

						if (entityRect.Intersects(thisRect) &&
							entity.EntityName == "Creature" && ((Creature)entity).MatingCandidate != this)
						{
							MatingCandidate = (Creature)entity;
						}
					}
				}
			}

			if (!LoseEnergy)
				Energy.SetToMax();

			if (Energy.Value <= 1000 || Age >= MaxAge)
				Kill();
		}

		protected static double GetDoubleFromGene(DoubleGene gene, double max)
		{
			return GetDoubleFromGene(gene, 0, max);
		}
		protected static double GetDoubleFromGene(DoubleGene gene, double min, double max)
		{
			return min + (max - min) * gene.Value;
		}
		protected static int GetIntFromGene(DoubleGene gene, int max)
		{
			return GetIntFromGene(gene, 0, max);
		}
		protected static int GetIntFromGene(DoubleGene gene, int min, int max)
		{
			return min + (int)Math.Round((max - min) * gene.Value, 0);
		}
		protected static T[] GetListSliceAsArray<T>(int from, int to, ICollection<T> l)
		{
			return l.Skip(from).Take(to - from).Cast<T>().ToArray();
		}
	}
}
